/*
    * Author:   Sander
    * Date:     16-apr-2011
    * Name:     OBJParser
    * Version:  1.0
    *
    * This code is property of Sander blahblahblah copyright blahblah.
*/


package javaraytracer.parsers;

import java.io.FileNotFoundException;
import java.util.ArrayList;
import javaraytracer.Materials.Material;
import javaraytracer.Objects.Mesh;
import javaraytracer.Objects.Sphere;
import javaraytracer.Objects.Triangle;
import javaraytracer.Utils;
import javaraytracer.Vec3;

//PENDING:add function/property description
public class OBJParser
{

	public static ParseData parse(String file, Vec3 position, Vec3 rotation, Material material, double scale)
	{
		String[] lines	= null;
		ArrayList<Vec3> v = new ArrayList<Vec3>();
		ArrayList<Vec3> vn = new ArrayList<Vec3>();
		ArrayList<Vec3> vt = new ArrayList<Vec3>();
		ArrayList<int[][]> f = new ArrayList<int[][]>();
		ArrayList<MaterialLibrary> materials = new ArrayList<MaterialLibrary>();
		int currentMaterial = 0;
		ArrayList<String> groups = new ArrayList<String>();
		ArrayList<ArrayList<Triangle>> smoothingGroup = new ArrayList<ArrayList<Triangle>>();
		int currentSmoothingGroup = -1;
		int currentGroup = 0;
		Triangle[][] triangles = null;

		int numberPolygons = 0;
		int tricounter = 0;

		//load file
		try
		{
			lines = ASCIILoader.load(file);
		}
		catch(FileNotFoundException e)
		{
			lines = new String[0];
			System.out.println(e);
		}

		for(int i = 0; i < lines.length; i++) //check keyword
		{
			if(lines[i].indexOf("mtllib") == 0)
			{
				//load material library
				String[] split = lines[i].split(" ");
				String dir = file.substring(0, file.lastIndexOf('/') + 1);
				try
				{
					materials.addAll( MTLParser.parse(ASCIILoader.load(dir + split[1]), dir) );
				}
				catch(FileNotFoundException e)
				{
					System.out.println(e);
				}
			}
			else if(lines[i].indexOf("g") == 0)
			{
				//set group
				String[] split = lines[i].split(" ");
				currentGroup = -1;
				for(int g = 0; g < groups.size(); g++)
				{
					if(groups.get(g).compareTo(split[1]) == 0)
					{
						currentGroup = g;
					}
				}
				if(currentGroup == -1)
				{
					groups.add(split[1]);
					currentGroup = groups.size() - 1;
				}
			}
			else if(lines[i].indexOf("o") == 0)
			{
				//set object, same treated as group. should research this.
				String[] split = lines[i].split(" ");
				currentGroup = -1;
				for(int g = 0; g < groups.size(); g++)
				{
					if(groups.get(g).compareTo(split[1]) == 0)
					{
						currentGroup = g;
					}
				}
				if(currentGroup == -1)
				{
					groups.add(split[1]);
					currentGroup = groups.size() - 1;
				}
			}
			else if(lines[i].indexOf("usemtl") == 0)
			{
				String name = lines[i].split(" ")[1];
				for(int j = 0; j < materials.size(); j++)
				{
					MaterialLibrary mat = materials.get(j);
					if(mat.name.compareTo(name) == 0)
					{
						currentMaterial = j;
					}
				}
			}
			else if(lines[i].indexOf("s") == 0)
			{
				//use smoothing group
				String[] split = lines[i].split(" ");
				if(split[1].compareTo("off") == 0)
				{
					currentSmoothingGroup = 0;
				}
				else
				{
					currentSmoothingGroup = i;
				}
			}
			else if(lines[i].indexOf("v ") == 0)
			{
				//vertice
				String[] split = lines[i].split(" ");
				v.add(position.add(new Vec3(Double.parseDouble(split[2]) * scale, Double.parseDouble(split[3]) * scale, Double.parseDouble(split[4]) * scale).rotate(rotation) ));
			}
			else if(lines[i].indexOf("vn") == 0)
			{
				//normal
				String[] split = lines[i].split(" ");
				vn.add(new Vec3(Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3])).rotate(rotation));
			}
			else if(lines[i].indexOf("vt") == 0)
			{
				//texture map
				String[] split = lines[i].split(" ");
				vt.add( Utils.wrapUVW(new Vec3(Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]))) );
			}
			else if(lines[i].indexOf("f") == 0)
			{
				//polygon
				//f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 [v4/vt4/vn4]
				String[] split = lines[i].split(" ");
				if(split[1].contains("/"))
				{
					String[] split21 = split[1].split("/");
					String[] split22 = split[2].split("/");
					String[] split23 = split[3].split("/");
					if(split.length == 4)
					{
						int[][] fpart = new int[][]
						{
							new int[]{Utils.parseInt(split21[0]), Utils.parseInt(split21[1]), Utils.parseInt(split21[2])},
							new int[]{Utils.parseInt(split22[0]), Utils.parseInt(split22[1]), Utils.parseInt(split22[2])},
							new int[]{Utils.parseInt(split23[0]), Utils.parseInt(split23[1]), Utils.parseInt(split23[2])},
							new int[]{currentMaterial, currentGroup, currentSmoothingGroup}
						};
						f.add(fpart);
						tricounter++;
					}
					else if(split.length == 5)
					{
						String[] split24 = split[4].split("/");
						int[][] fpart = new int[][]
						{
							new int[]{Utils.parseInt(split21[0]), Utils.parseInt(split21[1]), Utils.parseInt(split21[2])},
							new int[]{Utils.parseInt(split22[0]), Utils.parseInt(split22[1]), Utils.parseInt(split22[2])},
							new int[]{Utils.parseInt(split23[0]), Utils.parseInt(split23[1]), Utils.parseInt(split23[2])},
							new int[]{Utils.parseInt(split24[0]), Utils.parseInt(split24[1]), Utils.parseInt(split24[2])},
							new int[]{currentMaterial, currentGroup, currentSmoothingGroup}
						};
						f.add(fpart);
						tricounter += 2;
					}
				}
				else
				{
					//polygon
					//f v1 v2 v3 [v4]
					if(split.length == 4)
					{
						int[][] fpart = new int[][]
						{
							new int[]{Utils.parseInt(split[1])},
							new int[]{Utils.parseInt(split[2])},
							new int[]{Utils.parseInt(split[3])},
							new int[]{currentMaterial, currentGroup, currentSmoothingGroup}
						};
						f.add(fpart);
						tricounter++;
					}
					else if(split.length == 5)
					{
						int[][] fpart = new int[][]
						{
							new int[]{Utils.parseInt(split[1])},
							new int[]{Utils.parseInt(split[2])},
							new int[]{Utils.parseInt(split[3])},
							new int[]{Utils.parseInt(split[4])},
							new int[]{currentMaterial, currentGroup, currentSmoothingGroup}
						};
						f.add(fpart);
						tricounter += 2;
					}
				}

			}

		}

		numberPolygons = tricounter;

		triangles = new Triangle[groups.size()][numberPolygons];
		int nextf = 0;

		/* fpart int[][]
		 *	[0] int[] vertice
		 *		[0] int vertice pos
		 *		[1] int texture coordinates
		 *		[2] int normal
		 *	[1] int[] vertice
		 *		...
		 *	[2] int[] vertice
		 *		...
		 * //optional
		 *	[3] int[] vertice
		 *		...
		 * //material
		 *	[3/4] int[]
		 *		[0] int material
		 *		[1] string	group/object
		 *		[2] smoothing group
		 */
		Material mat = material;
		for(int j = 0; j < f.size(); j++)
		{
			boolean normals = false;
			boolean texture = false;
			int[][] fpart = f.get(j);
			Vec3 p1 = v.get(fpart[0][0] - 1);
			Vec3 p2 = v.get(fpart[1][0] - 1);
			Vec3 p3 = v.get(fpart[2][0] - 1);
			
			Vec3 n1 = Vec3.EMPTY, n2 = Vec3.EMPTY, n3 = Vec3.EMPTY, n4 = Vec3.EMPTY,
					t1 = Vec3.EMPTY, t2 = Vec3.EMPTY, t3 = Vec3.EMPTY, t4 = Vec3.EMPTY;
			
			if(fpart[0].length == 2)
			{
				texture = true;
			}
			if(fpart[0].length == 3)
			{
				texture = true;
				normals = true;
			}
			
			if(normals)
			{
				n1 = (fpart[0][2] == -1 ? Vec3.EMPTY : vn.get(fpart[0][2] - 1));
				n2 = (fpart[1][2] == -1 ? Vec3.EMPTY : vn.get(fpart[1][2] - 1));
				n3 = (fpart[2][2] == -1 ? Vec3.EMPTY : vn.get(fpart[2][2] - 1));
			}
			if(texture)
			{
				t1 = (fpart[0][1] == -1 ? Vec3.EMPTY : vt.get(fpart[0][1] - 1));
				t2 = (fpart[1][1] == -1 ? Vec3.EMPTY : vt.get(fpart[1][1] - 1));
				t3 = (fpart[2][1] == -1 ? Vec3.EMPTY : vt.get(fpart[2][1] - 1));
			}

			if(fpart.length == 4)
			{
				if(material == null)
				{
					mat = new Material(materials.get(fpart[3][0]));
				}
				if(texture) mat.offset = new Vec3[]{t1, t2, t3};
				currentSmoothingGroup = fpart[3][2];

				if(!normals || fpart[0][2] == -1) //if normals aint there
				{
					triangles[fpart[3][1]][nextf] = new Triangle(p1, p2, p3, mat);
				}
				else
				{
					triangles[fpart[3][1]][nextf] = new Triangle(p1, p2, p3, n1, n2, n3, mat);
				}
				triangles[fpart[3][1]][nextf].smoothingGroup = currentSmoothingGroup;
				
				nextf++;
			}
			else if(fpart.length == 5)
			{
				if(material == null)
				{
					mat = new Material(materials.get(fpart[4][0]));
				}
				currentSmoothingGroup = fpart[4][2];
				
				Vec3 p4 = v.get(fpart[3][0] - 1);
				if(normals) n4 = (fpart[3][2] == -1 ? Vec3.EMPTY : vn.get(fpart[3][2] - 1));
				if(texture) t4 = (fpart[3][1] == -1 ? Vec3.EMPTY : vt.get(fpart[3][1] - 1));

				if(!normals || fpart[0][2] == -1)
				{
					if(texture) mat.offset = new Vec3[]{t1, t2, t3};
					triangles[fpart[4][1]][nextf] = new Triangle(p1, p2, p3, mat);
					triangles[fpart[4][1]][nextf].smoothingGroup = currentSmoothingGroup;
					nextf++;
					Material mat2 = new Material(mat);
					if(texture) mat2.offset = new Vec3[]{t1, t3, t4};
					triangles[fpart[4][1]][nextf] = new Triangle(p1, p3, p4, mat2);
					triangles[fpart[4][1]][nextf].smoothingGroup = currentSmoothingGroup;
					nextf++;
				}
				else
				{
					if(texture) mat.offset = new Vec3[]{t1, t2, t3};
					triangles[fpart[4][1]][nextf] = new Triangle(p1, p2, p3, n1, n2, n3, mat);
					triangles[fpart[4][1]][nextf].smoothingGroup = currentSmoothingGroup;
					nextf++;
					Material mat2 = new Material(mat);
					if(texture) mat2.offset = new Vec3[]{t1, t3, t4};
					triangles[fpart[4][1]][nextf] = new Triangle(p1, p3, p4, n1, n3, n4, mat2);
					triangles[fpart[4][1]][nextf].smoothingGroup = currentSmoothingGroup;
					nextf++;
				}
			}
		}
		
		Mesh[] meshes = new Mesh[groups.size()];
		for(int m = 0; m < meshes.length; m++)
		{
			meshes[m] = new Mesh(triangles[m], groups.get(m));
		}

		// bounding sphere
		double minX = Double.MAX_VALUE;
		double maxX = Double.MIN_VALUE;
		double minY = Double.MAX_VALUE;
		double maxY = Double.MIN_VALUE;
		double minZ = Double.MAX_VALUE;
		double maxZ = Double.MIN_VALUE;
		for(int k = 0; k < v.size(); k++)
		{
			Vec3 p = v.get(k);
			if(p.x < minX) minX = p.x;
			if(p.x > maxX) maxX = p.x;
			if(p.y < minY) minY = p.y;
			if(p.y > maxY) maxY = p.y;
			if(p.z < minZ) minZ = p.z;
			if(p.z > maxZ) maxZ = p.z;
		}

		Vec3 origin = new Vec3((minX + maxX) / 2.0, (minY + maxY) / 2.0, (minZ + maxZ) / 2.0);
		float radius = (float)(new Vec3(maxX, maxY, maxZ).sub(origin)).length();

		Sphere boundingSphere = new Sphere(origin, radius, Material.WHITE);

		ParseData data = new ParseData(meshes, boundingSphere);
		return data;
	}

}
